package com.studyprojectbacked.service.impl;

import com.studyprojectbacked.entity.Account;
import com.studyprojectbacked.entity.dto.RegisterAccount;
import com.studyprojectbacked.mapper.UserMapper;
import com.studyprojectbacked.service.AuthorizeService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Service
public class AuthorizeServiceImpl implements AuthorizeService {

  @Value("${spring.mail.username}")
  String from;

  @Autowired
  UserMapper userMapper;

  @Resource
  MailSender mailSender;

  @Resource
  StringRedisTemplate stringRedisTemplate;

  BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

  @Override
  public UserDetails loadUserByUsername(String nameOrEmail) throws UsernameNotFoundException {
    if (!StringUtils.hasText(nameOrEmail))
      throw new UsernameNotFoundException("用户名或邮箱不能为空");

    Account account = userMapper.findAccountByNameOrEmail(nameOrEmail);
    if (account == null)
      throw new UsernameNotFoundException("用户名或密码错误");

    return User
        .withUsername(account.getUsername())
        .password(account.getPassword())
        .roles("user")
        .build();
  }

  /**
   * 发送验证码到邮箱
   * 1. 先生成对应的验证码
   * 2. 把邮箱和对应的验证码直接放到Redis里 (过期时间3分钟, 如果此时重新要求发邮件,
   *    那么剩余时间低于2分钟，就可以重新发一次，重复此过程)
   * 3. 发送验证码到指定邮箱
   * 4. 如果发送失败, 把Redis刚插入的验证码删除
   * 5. 用户注册时. 再从Redis里面取出键值对，然后验证是否一致
   */
  @Override
  public String sendCodeToEmail(String email, String sessionId, boolean hasAccount) {
    String key = redisKey(sessionId, email, ":" + hasAccount);

    if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
      Long expire = Optional
          .ofNullable(stringRedisTemplate.getExpire(key, TimeUnit.SECONDS))
          .orElse(0L);

      if (expire > 120) return "请求过于频繁";
    }

    Account account = userMapper.findAccountByNameOrEmail(email);
    // 重置密码要求账户存在，但是账户为空
    if (hasAccount && account == null) return "邮箱地址未注册";
    // 注册账户要求账户不存在，但是账户已注册
    if (!hasAccount && account != null) return "邮箱地址已注册";

    Random random = new Random();
    int code = random.nextInt(900000) + 100000;

    SimpleMailMessage message = new SimpleMailMessage();
    message.setFrom(from);
    message.setTo(email);
    message.setSubject("您的验证邮件");
    message.setText("验证码: " + code);

    try {
      mailSender.send(message);
      stringRedisTemplate.opsForValue().set(key, String.valueOf(code), 3, TimeUnit.MINUTES);
      return null;
    } catch (MailException e) {
      e.printStackTrace();
      return "邮箱发送失败，请检查邮箱是否有效";
    }
  }

  @Override
  public String validateAndRegister(RegisterAccount ra, String sessionId) {
    String key = redisKey(sessionId, ra.getEmail(), ":false");

    // 判断Redis是否含有这个键值对
    if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
      String code = stringRedisTemplate.opsForValue().get(key);

      // 判断键值对是否有效，可能刚好过期了
      if (code == null) return "验证码失效，请重新请求";

      // 判断验证码是否正确
      if (code.equals(ra.getCode())) {
        Account account = userMapper.findAccountByNameOrEmail(ra.getUsername());
        if (account != null) return "用户名已被注册";

        ra.setPassword(encoder.encode(ra.getPassword()));

        // 判断是否成功插入用户
        if (userMapper.createAccount(ra) > 0) {
          stringRedisTemplate.delete(key);
          return null;
        }
        else {
          return "服务器出错，请联系管理员";
        }
      } else {
        return "验证码错误，请检查后再提交";
      }
    } else {
      return "请先请求一封验证码邮件";
    }
  }

  @Override
  public String validateCode(String email, String code, String sessionId) {
    String key = redisKey(sessionId, email, ":true");

    System.out.println(key);
    if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
      String redisCode = stringRedisTemplate.opsForValue().get(key);

      // 判断键值对是否有效，可能刚好过期了
      if (redisCode == null) return "验证码失效，请重新请求";

      if (redisCode.equals(code)) {
        stringRedisTemplate.delete(key);
        return null;
      } else {
        return "验证码错误，请检查后提交";
      }
    } else {
      return "请先请求一封验证码邮件";
    }
  }

  @Override
  public boolean resetPassword(String password, String email) {
    password = encoder.encode(password);
    return userMapper.resetPasswordByEmail(password, email) > 0;
  }

  private String redisKey(String sessionId, String email, String other) {
    return "email:" + sessionId + ":" + email + other;
  }
}
